/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/variable/Module_interface.h"
#include "simodo/variable/VariableSetWrapper.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/variable/json/Serialization.h"

#include <memory>
#include <filesystem>
#include <cassert>
#include <algorithm>

#ifdef CROSS_WIN
// MinGW related workaround
#define BOOST_DLL_FORCE_ALIAS_INSTANTIATION
#endif

#include <boost/dll/alias.hpp>

using namespace simodo;
using namespace simodo::variable;
using namespace simodo::inout;

namespace fs = std::filesystem;

namespace
{
    Value setup(Module_interface * host, const VariableSetWrapper & args);
    Value symbols(Module_interface * host, const VariableSetWrapper & args);
    Value produceTokens(Module_interface * host, const VariableSetWrapper & args);
}

class MainTokenizer : public Module_interface
{
    // ModuleFactory_interface * _factory;
    Value                     _substitutions_value;

public:
    // MainTokenizer(ModuleFactory_interface * factory) : _factory(factory) {}

    Value setup(const std::string & path_to_data, const std::string & language);
    Value symbols();
    Value produceTokens(const std::u16string & text_to_parse, int position, context_index_t context);

    virtual version_t version() const override { return lib_version(); }

    virtual Object instantiate(std::shared_ptr<variable::Module_interface> module_object) override
    {
        return Object {{
            // {u"version", u"0.1"},
            {u"specialization", u"Word"},
            {u"setup", {ValueType::Function, Object {{
                {u"@", ExternalFunction {module_object, ::setup}},
                {{}, ValueType::String},
                {u"path_to_data", ValueType::String},
                {u"language", ValueType::String},
            }}}},
            {u"symbols", {ValueType::Function, Object {{
                {u"@", ExternalFunction {module_object, ::symbols}},
                {{}, ValueType::Array},
            }}}},
            {u"produceTokens", {ValueType::Function, Object {{
                {u"@", ExternalFunction {module_object, ::produceTokens}},
                {{}, ValueType::Object},
                {u"text_to_parse", ValueType::String},
                {u"position", ValueType::Int},
                {u"context", ValueType::Int},
            }}}},
        }};
    }

    // virtual ModuleFactory_interface * factory() override { return _factory; }

    // Factory method
    static std::shared_ptr<Module_interface> create() {
        return std::make_shared<MainTokenizer>();
    }
};

BOOST_DLL_ALIAS(
    MainTokenizer::create,    // <-- this function is exported with...
    create_simodo_module      // <-- ...this alias name
)

namespace
{
    Value setup(Module_interface * host, const VariableSetWrapper & args)
    {
        // Эти условия должны проверяться в вызывающем коде и при необходимости выполняться преобразования
        assert(host != nullptr);
        assert(args.size() == 2);
        assert(args[0].value().type() == ValueType::String);
        assert(args[1].value().type() == ValueType::String);

        MainTokenizer * main = static_cast<MainTokenizer *>(host);
        return main->setup(toU8(args[0].value().getString()),
                           toU8(args[1].value().getString()));
    }

    Value symbols(Module_interface * host, const VariableSetWrapper & )
    {
        // Эти условия должны проверяться в вызывающем коде и при необходимости выполняться преобразования
        assert(host != nullptr);

        MainTokenizer * main = static_cast<MainTokenizer *>(host);
        return main->symbols();
    }

    Value produceTokens(Module_interface * host, const VariableSetWrapper & args)
    {
        // Эти условия должны проверяться в вызывающем коде и при необходимости выполняться преобразования
        assert(host != nullptr);
        assert(args.size() == 3);
        assert(args[0].value().type() == ValueType::String);
        assert(args[1].value().type() == ValueType::Int);
        assert(args[2].value().type() == ValueType::Int);

        MainTokenizer *        main = static_cast<MainTokenizer *>(host);
        context_index_t context = NO_TOKEN_CONTEXT_INDEX;
        if (args[2].value().getInt() >= 0 )
            context = static_cast<context_index_t>(args[2].value().getInt());

        return main->produceTokens(args[0].value().getString(), args[1].value().getInt(), context);
    }

}

Value MainTokenizer::setup(const std::string & path_to_data, const std::string & language)
{
    fs::path path_to_substitutions = path_to_data;
    path_to_substitutions /= "substitutions/" + language + ".json";

    if (!fs::exists(path_to_substitutions))
        return {};

    loadJson(path_to_substitutions.string(), _substitutions_value);
    if (_substitutions_value.type() == ValueType::Object)
        return u"";

    return {}; // Не ОК
}

Value MainTokenizer::symbols()
{
    if (_substitutions_value.type() != ValueType::Object)
        return {};

    std::vector<variable::Value> result; 

    const std::shared_ptr<Object> substitutions = _substitutions_value.getObject();
    for(const Variable & sub : substitutions->variables()) {
        if (sub.type() != ValueType::Array)
            continue;
        const std::shared_ptr<Array> array = sub.value().getArray();
        for(const variable::Value & v : array->values()) {
            if (v.type() != variable::ValueType::String)
                continue;
            Object symbol_data {{
                {u"symbol",   v.getString()},
                {u"type",     sub.name()},
                }};
            result.push_back(symbol_data);
        }
    }

    return result;
}

Value MainTokenizer::produceTokens(const std::u16string & text_to_parse, int position, context_index_t )
{
    if (_substitutions_value.type() != ValueType::Object)
        return {};

    Array  tokens;
    const std::shared_ptr<Object> substitutions = _substitutions_value.getObject();
    for(const Variable & sub : substitutions->variables()) {
        if (sub.type() != ValueType::Array)
            continue;
        const std::shared_ptr<Array> array = sub.value().getArray();
        auto it = std::find_if(array->values().begin(), array->values().end(), 
                               [text_to_parse](const Value & x) 
                {
                    return x.type() == ValueType::String && x.getString() == text_to_parse;
                });
        if (it != array->values().end()) {
            tokens.values().push_back(Object {{
                {u"token",    text_to_parse},
                {u"type",     sub.name()},
                {u"position", position},
            }});
            break;
        }
    }

    return Object {{
        {u"tokens", tokens},
        }};
}

